TCP 的三次握手和四次挥手
什么是三次握手?为什么需要三次握手?
三次握手其实就是指在建立一次 TCP 连接时,需要客户端和服务器总共发送三个包。
进行三次握手的主要作用:
- 确认通信双方的接收能力和发送能力是否正常。
- 指定自己的初始化序列号为后面的可靠性传送做准备。(连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息)
三次握手的过程
开始时,客户端处于 Closed 状态,服务端处于 Listen 状态。
- 第一次握手:
客户端向服务器发送一个 SYN 报文,并指明客户端的初始化序列号 ISN,此时客户端处于 SYN_SEND 状态。
TIP
首部的同步位 SYN=1,初始序号 seq=x,SYN=1 的报文段不能携带数据,但要消耗掉一个序号。
- 第二次握手:
服务器收到 SYN 报文后,会以自己的 SYN 报文作为应答,也会指定自己的初始化序列号 ISN。同时把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 状态。
TIP
在确认报文段中 SYN=1,ACK=1,确认号 ack=x+1,初始序号 seq=y
- 第三次握手:
客户端收到 SYN 报文后,会发送一个 ACK 报文(值为服务器的 ISN + 1),表示已经收到了服务器的 SYN 报文,此时客户端处于 ESTABLISHED 状态。
服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方成功建立连接。
TIP
确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。
简单来说就是以下三步:
- 第一次握手: 客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
- 第二次握手: 服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
- 第三次握手: 当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
TCP 三次握手的建立连接的过程其实就是相互确认初始序号的过程,告诉对方,什么样序号的报文段能够被正确接收。
第三次握手的作用是客户端对服务器端的初始序号的确认。如果只使用两次握手,那么服务器就没有办法知道自己的序号是否已被确认。
同时这样也是为了防止失效的请求报文段被服务器接收,而出现错误的情况。
为什么不能两次握手呢?
假设客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。
后来收到了确认,成建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端。
但第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接。
不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端会忽略服务端发来的确认,也不发送数据,则服务端会一直等待客户端发送数据,浪费资源。
四次挥手
开始时,双方都处于 ESTABLISHED 状态,加入是客户端先发起关闭请求。
四次挥手的过程如下:
- 第一次挥手:客户端发送一个 FIN 报文,报文中指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
TIP
即先发出连接释放报文段(FIN = 1,序号 seq = u),再停止发送数据,主动关闭 TCP 连接,接着进入 FIN_WAIT1(终止等待1)状态,等待服务端确认。
- 第二次挥手:服务器收到 FIN 之后,发送 ACK 报文,并把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表示已经收到了客户端的报文,此时服务器处于 CLOSE_WAIT 状态。
TIP
即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。
- 第三次挥手:如果此时服务器也想断开连接了,和客户端的第一次挥手一样,向客户端发送 FIN 报文,并指定一个序列号。此时服务器处于 LAST_ACK 状态。
TIP
即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。
- 第四次挥手:客户端收到 FIN 后,同样会发送一个 ACK 报文作为应答,并把服务端的序列号值 + 1 作为自己的 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。一段时间过后,在确保服务端收到了自己的 ACK 报文后,客户端进入 CLOSED 状态,服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED 状态。
TIP
即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。
简单来说就是以下四步:
第一次挥手:若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。
第二次挥手:服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。
第三次挥手:服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。
第四次挥手: 客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。
TCP 使用四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代表不能再向对方发送数据,连接处于的是半释放的状态。
最后一次挥手中,客户端会等待一段时间再关闭的原因,是为了防止发送给服务器的确认报文段丢失或者出错,从而导致服务器端不能正常关闭。
为什么需要四次挥手呢?
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
但在关闭连接时,当服务端收到 FIN 报文后,很可能并不会立即关闭SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四次挥手。